Kotlin 常用扩展函数

let run also with apply takeIf使用

let

let对对象T的扩展 ,接收的是T类型,返回的是R类型 。

  • 有自己的作用域
  • 作用域中的接收者是it
  • 返回值,返回最后一个对象
1
2
3
4
5
 val result ="Hello World".let {
println(it)
"Hello"
}
println(result)

输出结果

1
2
System.out: Hello World
System.out: Hello

使用?.let进行判断

1
2
3
4
5
6
7
private fun getInfo(jsonObject: JSONObject?): Info? {
return jsonObject?.let {
val uri = it.optString("uri")
val length = it.optString("length")
Info(uri, length)
}
}

这样写和用if判空的写法没有区别,实际上根据这段代码编译出的字节码反编译得到的java代码就是if…else…形式的。

在多重判断的时候使用,代码看起来更简洁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// if...else...写法
private fun testIfElse(): Any? {
return if (a !== null) {
val b = handleA(a)
if (b !== null) {
handleB(b)
} else {
null
}
} else {
null
}
}
// ?.let写法
private fun testLet(): Any? {
return a?.let { handleA(it) }?.let { handleB(it) }
}

let同时多重判断

1
2
3
4
5
6
7
8
9
10
11
12
13
 private fun multiJudge(a:String?,b:String?){
a?.let{
b?.let{
// code
}
}
}

private fun multiJudge(a:String?,b:String?){
safeLet(a,b){a,b ->
// code
}
}
注意

let在括号里是可以进行重新命名的,但是箭头后面不能加大括号。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
fun letTest(a:String?){
a?.let{ renameA ->
// code
}
}

// 错误
fun letTest(a:String?){
a?.let{ renameA -> {
// code
}
}
}

run

  • 有自己的作用域
  • 作用域中的接收者是this
  • 返回值,返回最后一个对象
1
2
3
4
5
6
7
8
9
10


fun scope(){
var animal = "cat"
run {
val animal = "dog"
println(animal) // dog
}
println(animal) //cat
}

run拥有一个单独的作用域,能够重新定义一个animal变量,并且它的作用域只存在于run函数当中。看起来好像没什么用处

1
2
3
run {
if (islogin) loginDialog else getAwardDialog
}.show()

他的返回值是这个作用域的最后一个对象,上面的代码的话就会更简洁一些。而不是调用两次

1
2
3
4
5
6
7
stringVariable?.run {
println("字符串的长度为$length")
}

stringVariable?.let {
println("字符串的长度为 ${it.length}")
}

和let的区别
能够为it重新命名。run不能够重新命名,如果我们不想覆盖外部作用域的this,这时候去使用T.let会更加的方便

also

  • 有自己的作用域
  • 作用域中的接收者 it
  • 返回值都是原来的对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    val original = "abc"
    original.also {
    println("The original String is $it") // "abc"
    it.reversed()
    }.also {
    println("The reverse String is ${it}") // "abc"
    it.length
    }.also {
    println("The length of the String is ${it}") // "abc"
    }

T.also不管调用多少次返回的都是原来的original对象
对于T.let和T.also都能够进行链式操作,那么我们现在结合一下T.let和T.also的链式调用来看一下在实际场景中的应用。
//原始函数

1
2
3
4
5
6
7
8
fun makeDir(path: String): File  {
val result = File(path)
result.mkdirs()
return result
}

//通过let和also的链式调用改进后的函数
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

apply

  • 有自己的作用域
  • 作用域中的接收者 this
  • 返回值都是原来的对象

apply 和also的区别相当于 let 和run 的区别。一个接收this ,另一个是接收it 。同样的情况,also 是用重新命名。apply则不行。是链式结构创建一个Intent

1
2
3
4
5
6
7
8
9
10
11
12
// 普通创建Intent方法
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data=Uri.parse(intentData)
return intent
}

// 通过apply函数的链式调用创建Intent
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }

使用于建造者模式,apply则更经常使用。或者对同一对象多次操作,则apply 显现的代码更加的简洁。返回的是对象本身

1
2
3
4
5
return TextView(context).apply {
text = "test"
setOnClickListener(onClickListener)
// 更多对TextView的操作
}

with

与run的方法类似 ,with(T)函数,而另一个则是使用了T.run函数。
with是传参进行处理。对于没什么判断的操作的时候使用情况都差不多

1
2
3
4
5
6
7
8
9
with(webView.settings){
javaScriptEnabled = true
databaseEnabled = true
}

webView.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}

假设一种场景,那就是webView.settings可能为null。那我们就来再次看一下下面这段代码.

1
2
3
4
5
6
7
8
9
with(webView.settings){
javaScriptEnabled = true
databaseEnabled = true
}

webView.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}

很明显run方法更好,可以在函数使用之前进行判断

takeIf

增加了判断,

1
2
3
4
5
6
7
File(url).takeIf { it.exists() }
?.let {
JSONObject(NetworkUtils.postFile(SERVER_URL, url))
}?.takeIf { it.optString("message") == "success" }
?.let {
post(it.optString("result"))
} ?: mHandler.post { view?.onFail() }

takeIf 操作最后对象为Boolean ,当值是false时,则返回null ,返回值为true 则返回自身

在这里我们通过一个树状图来看一下对着五个标准函数的区别,使用以及如何选取标准函数

enter image description here